/**
 * 
 * Copyright (C) 2011 Cody Stoutenburg . All rights reserved.
 *
 *       This program is free software; you can redistribute it and/or
 *       modify it under the terms of the GNU Lesser General Public License
 *       as published by the Free Software Foundation; either version 2.1
 *       of the License, or (at your option) any later version.
 *
 *       This program is distributed in the hope that it will be useful,
 *       but WITHOUT ANY WARRANTY; without even the implied warranty of
 *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *       GNU Lesser General Public License for more details.
 *
 *       You should have received a copy of the GNU Lesser General Public License
 *       along with this program; if not, write to the Free Software
 *       Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 * 
 */
package ca.usherbrooke.agent.controller.shared;

import java.awt.Component;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.util.LinkedList;

import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.filechooser.FileNameExtensionFilter;

import org.newdawn.slick.geom.Rectangle;

import ca.usherbrooke.agent.controller.BasicAgentLauncher;
import ca.usherbrooke.behaviours.controller.request.RequestAttack;
import ca.usherbrooke.behaviours.controller.request.RequestDropObject;
import ca.usherbrooke.behaviours.controller.request.RequestGrapObject;
import ca.usherbrooke.behaviours.controller.request.RequestJump;
import ca.usherbrooke.behaviours.controller.request.RequestPutDownProtection;
import ca.usherbrooke.behaviours.controller.request.RequestPutDownWeapon;
import ca.usherbrooke.behaviours.controller.request.RequestSetSpeed;
import ca.usherbrooke.behaviours.controller.request.RequestSetTargetPosition;
import ca.usherbrooke.behaviours.controller.request.RequestTakeProtection;
import ca.usherbrooke.behaviours.controller.request.RequestTakeWeapon;
import ca.usherbrooke.goal.EntityGoal;
import ca.usherbrooke.mainApp.MainAppAgent;
import ca.usherbrooke.mainApp.view.simulation.PanelSimulation;
import ca.usherbrooke.model.entity.IEntityModel;
import ca.usherbrooke.model.world.IWorldModel;
import ca.usherbrooke.tools.position.Vector2D;

/**
 * @author Cody Stoutenburg
 * 
 */
public abstract class IAgent implements Serializable {
	private static final long serialVersionUID = 1L;

	private final BasicAgentLauncher realAgent;
	private final ExternalBehaviours allUserBehaviour;

	private final LinkedList<IMessage> inbox;

	/**
	 * You must do nothing more in the constructor than call super constructor
	 * 
	 * @param agent
	 */
	public IAgent(BasicAgentLauncher agent) {
		realAgent = agent;

		inbox = new LinkedList<IMessage>();

		allUserBehaviour = new ExternalBehaviours(agent);
	}

	/**
	 * this method is call once when the agent is setup so you can initialize
	 * here what you want.
	 */
	public void initEntity() {
		this.realAgent.addBehaviour(allUserBehaviour);

		if (haveMemory()) {
			loadMemory();
		}
	}

	/**
	 * this method is call once when the agent is going to be deleted
	 */
	public void entityDied() {
		if (haveMemory()) {
			saveMemory();
		}

		JPanel myPanel = this.getEntityPanel();
		if (myPanel != null) {
			myPanel.removeAll();
		}
	}

	/**
	 * This method is call every time world change, if you want to do something
	 * with this just override the method
	 * 
	 * the action you do here should be quick to keep the agent reactive
	 * 
	 * @param world
	 */
	public void worldChanged(IWorldModel world) {
	}

	/**
	 * 
	 * @return true if we can load and save memory of an agent
	 */
	public boolean haveMemory() {
		return false;
	}

	/**
	 * this method should call loadMemory(File f) with the memory file
	 * 
	 * the default implementation open a file chooser so the user choose a .mem
	 * file
	 */
	protected void loadMemory() {
		JFileChooser chooser = new JFileChooser(new File("."));
		FileNameExtensionFilter filter = new FileNameExtensionFilter(
				"memories files", "mem");
		chooser.setFileFilter(filter);
		chooser.setSelectedFile(new File("memory.mem"));
		int returnVal = chooser.showOpenDialog(null);
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			File f = chooser.getSelectedFile();
			try {
				this.loadMemory(f);
			} catch (FileNotFoundException ex) {
				ex.printStackTrace();
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
	}

	/**
	 * this method should call saveMemory(File f) with the memory file
	 * 
	 * the default implementation open a file chooser so the user choose a .mem
	 * file
	 */
	protected void saveMemory() {
		JFileChooser chooser = new JFileChooser(new File("."));
		FileNameExtensionFilter filter = new FileNameExtensionFilter(
				"memories files", "mem");
		chooser.setFileFilter(filter);
		chooser.setSelectedFile(new File("memory.mem"));
		int returnVal = chooser.showOpenDialog(null);
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			File f = chooser.getSelectedFile();
			try {
				this.saveMemory(f);
			} catch (FileNotFoundException ex) {
				ex.printStackTrace();
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
	}

	/**
	 * this method is call when someone ask to save the memory of an entity
	 * 
	 * the default implementation do nothing
	 * 
	 * @param f
	 *            where to save the memory
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public void saveMemory(File f) throws FileNotFoundException, IOException {
	}

	public void loadMemory(File f) throws FileNotFoundException, IOException {
	}

	// Getter methods
	/**
	 * get the current world seen by the entity
	 * 
	 * @return the current world seen by the entity
	 */
	public final IWorldModel getWorld() {
		return realAgent.getWorld();
	}

	/**
	 * get the goals of the entity
	 * 
	 * @return the goals of the entity
	 */
	public final EntityGoal getGoals() {
		return realAgent.getGoals();
	}

	/**
	 * get the entity linked with this agent
	 * 
	 * @return the entity linked with the agent
	 */
	public final IEntityModel getEntity() {
		return realAgent.getEntity();
	}

	// SETTERS methods
	/**
	 * this method ask to real entity to stop at the target position at the
	 * center position
	 * 
	 * @param center
	 */
	public final void setTargetCenterPosition(Vector2D center) {
		Vector2D targetCenter = center.clone();
		Rectangle size = getEntity().getSize();
		targetCenter.addX(-(size.getWidth() / 2));
		targetCenter.addY(-(size.getHeight() / 2));
		setTargetPosition(targetCenter);
	}

	/**
	 * this method ask to real entity to stop at the target position
	 * 
	 * @param upperLeftCorner
	 */
	public final void setTargetPosition(Vector2D upperLeftCorner) {
		realAgent.addBehaviour(new RequestSetTargetPosition(realAgent,
				upperLeftCorner));
	}

	/**
	 * this method ask to set speed to the maximum speed with the given
	 * direction to the real entity
	 * 
	 * @param direction
	 */
	public final void setMaxSpeed(Vector2D direction) {
		Vector2D speed = direction.clone();
		speed.normalize();

		speed = speed.multiply(getEntity().getMaxSpeed());
		this.setSpeed(speed);
	}

	/**
	 * this method ask to set the speed of the real entity
	 * 
	 * @param speed
	 */
	public final void setSpeed(Vector2D speed) {
		realAgent.addBehaviour(new RequestSetSpeed(realAgent, speed));
	}

	/**
	 * this method ask to stop the speed of the real entity
	 */
	public final void stopMove() {
		setSpeed(Vector2D.NULL_VECTOR);
	}

	/**
	 * this method ask the real entity to jump
	 */
	public final void jump() {
		realAgent.addBehaviour(new RequestJump(realAgent));
	}

	/**
	 * this method ask the real entity to grab an object
	 * 
	 * @param objectGuid
	 */
	public final void grabObject(Long objectGuid) {
		realAgent.addBehaviour(new RequestGrapObject(realAgent, objectGuid));
	}

	/**
	 * this method ask the real entity to drop an object
	 * 
	 * @param objectGuid
	 */
	public final void dropObject(Long objectGuid) {
		realAgent.addBehaviour(new RequestDropObject(realAgent, objectGuid));
	}

	/**
	 * this method ask the real entity to attack the entity
	 * 
	 * @param entityGuid
	 */
	public final void attack(Long entityGuid) {
		realAgent.addBehaviour(new RequestAttack(realAgent, entityGuid));
	}

	/**
	 * this method ask the real entity to equip the protection
	 * 
	 * @param protectionGuid
	 */
	public final void takeProtection(Long protectionGuid) {
		realAgent.addBehaviour(new RequestTakeProtection(realAgent,
				protectionGuid));
	}

	/**
	 * this method ask the real entity to put the protection back in the
	 * equipment
	 * 
	 * @param protectionGuid
	 */
	public final void putDownProtection(Long protectionGuid) {
		realAgent.addBehaviour(new RequestPutDownProtection(realAgent,
				protectionGuid));
	}

	/**
	 * this method ask the real entity to equip the weapon
	 * 
	 * @param weaponGuid
	 */
	public final void takeWeapon(Long weaponGuid) {
		realAgent.addBehaviour(new RequestTakeWeapon(realAgent, weaponGuid));
	}

	/**
	 * this method ask the real entity to put the weapon back in the equipment
	 * 
	 * @param weaponGuid
	 */
	public final void putDownWeapon() {
		realAgent.addBehaviour(new RequestPutDownWeapon(realAgent));
	}

	public final void sendMessage(Long entityGuid, IMessage msg) {
		realAgent.sendMessage(entityGuid, msg);
	}

	/**
	 * this method should not be call by the user
	 * 
	 * @param msg
	 */
	public final void doReceiveMessage(IMessage msg) {
		inbox.add(msg);
	}

	/**
	 * get the first message of the queue and remove it
	 * 
	 * @return the first message received
	 */
	public final IMessage popMessage() {
		IMessage msg = null;
		if (!inbox.isEmpty()) {
			msg = inbox.removeFirst();
		}
		return msg;
	}

	/**
	 * reput the message as first message in queue
	 * 
	 * @param msg
	 */
	public final void putBack(IMessage msg) {
		inbox.addFirst(msg);
	}

	/**
	 * add a behaviour
	 * 
	 * @param b
	 *            behaviour to add
	 */
	public final void addBehaviour(IBehaviour<?> b) {
		this.allUserBehaviour.addBehaviour(b);
	}

	/**
	 * add a behaviour
	 * 
	 * @param b
	 *            behaviour to add
	 */
	public final void addBehaviour(IBehaviourOnWorldChanged<?> b) {
		this.allUserBehaviour.addBehaviour(b);
	}

	/**
	 * this method get the JPanel that the entity can use to print whatever he
	 * want. <br/>
	 * it is commonly use to print debug info
	 * 
	 * @return
	 */
	protected JPanel getEntityPanel() {
		JPanel panel = null;

		Component[] comps = MainAppAgent.APP_FRAME.getMainPanel()
				.getCenterPanel().getComponents();

		for (Component component : comps) {
			if (component instanceof PanelSimulation) {
				panel = ((PanelSimulation) component)
						.getPanelForEntity(realAgent.getEntityGUID());
			}
		}
		return panel;
	}
}
